/*******************************************************************************
 * Copyright (c) 2000, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.draw2d;

import java.util.ArrayList;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.rap.swt.graphics.ColorUtil;
import org.eclipse.draw2d.rap.swt.graphics.FontUtil;
import org.eclipse.rap.rwt.SingletonUtil;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Shell;


/**
 * Provides miscellaneous Figure operations.
 */
public class FigureUtilities {

  private static final float RGB_VALUE_MULTIPLIER = 0.6f;
  // [RAP AM] remove static usage
  private GC gc;
  private Font appliedFont;
  private FontMetrics metrics;

  // private static Color ghostFillColor = new Color(null, 31, 31, 31);
  private static Color ghostFillColor() {
    return ColorUtil.getColor( 31, 31, 31 );
  }

  private static FigureUtilities instance() {
    return SingletonUtil.getSessionInstance( FigureUtilities.class );
  }

  /**
   * Returns a new Color the same as the passed color in a darker hue.
   * 
   * @param color the color to darken
   * @return the darkened color
   * @since 2.0
   */
  public static Color darker( Color color ) {
    return new Color( null,
                      ( int )( color.getRed() * RGB_VALUE_MULTIPLIER ),
                      ( int )( color.getGreen() * RGB_VALUE_MULTIPLIER ),
                      ( int )( color.getBlue() * RGB_VALUE_MULTIPLIER ) );
  }

  /**
   * Returns the FontMetrics associated with the passed Font.
   * 
   * @param f the font
   * @return the FontMetrics for the given font
   * @see GC#getFontMetrics()
   * @since 2.0
   */
  public static FontMetrics getFontMetrics( Font f ) {
    setFont( f );
    FigureUtilities fu = instance();
    if( fu.metrics == null )
      fu.metrics = getGC().getFontMetrics();
    return fu.metrics;
  }

  /**
   * Returns the GC used for various utilities. Advanced graphics must not be switched on by clients
   * using this GC.
   * 
   * @deprecated do not mess with this GC
   * @return the GC
   */
  protected static GC getGC() {
    FigureUtilities fu = instance();
    if( fu.gc == null ) {
      fu.gc = new GC( new Shell() );
      fu.appliedFont = fu.gc.getFont();
    }
    return fu.gc;
  }

  /**
   * Returns the dimensions of the String <i>s</i> using the font <i>f</i>. Tab expansion and
   * carriage return processing are performed.
   * 
   * @param s the string
   * @param f the font
   * @return the text's dimensions
   * @see GC#textExtent(String)
   */
  protected static org.eclipse.swt.graphics.Point getTextDimension( String s, Font f ) {
    setFont( f );
    return getGC().textExtent( s );
  }

  /**
   * Returns the highest ancestor for the given figure
   * 
   * @since 3.0
   * @param figure a figure
   * @return the root ancestor
   */
  public static IFigure getRoot( IFigure figure ) {
    while( figure.getParent() != null )
      figure = figure.getParent();
    return figure;
  }

  /**
   * Returns the dimensions of the String <i>s</i> using the font <i>f</i>. No tab expansion or
   * carriage return processing will be performed.
   * 
   * @param s the string
   * @param f the font
   * @return the string's dimensions
   * @see GC#stringExtent(java.lang.String)
   */
  protected static org.eclipse.swt.graphics.Point getStringDimension( String s, Font f ) {
    setFont( f );
    return getGC().stringExtent( s );
  }

  /**
   * Returns the Dimensions of the given text, converting newlines and tabs appropriately.
   * 
   * @param text the text
   * @param f the font
   * @return the dimensions of the given text
   * @since 2.0
   */
  public static Dimension getTextExtents( String text, Font f ) {
    return new Dimension( getTextDimension( text, f ) );
  }

  /**
   * Returns the Dimensions of <i>s</i> in Font <i>f</i>.
   * 
   * @param s the string
   * @param f the font
   * @return the dimensions of the given string
   * @since 2.0
   */
  public static Dimension getStringExtents( String s, Font f ) {
    return new Dimension( getStringDimension( s, f ) );
  }

  /**
   * Returns the Dimensions of the given text, converting newlines and tabs appropriately.
   * 
   * @param s the string
   * @param f the font
   * @param result the Dimension that will contain the result of this calculation
   * @since 2.0
   */
  public static void getTextExtents( String s, Font f, Dimension result ) {
    org.eclipse.swt.graphics.Point pt = getTextDimension( s, f );
    result.width = pt.x;
    result.height = pt.y;
  }

  /**
   * Returns the width of <i>s</i> in Font <i>f</i>.
   * 
   * @param s the string
   * @param f the font
   * @return the width
   * @since 2.0
   */
  public static int getTextWidth( String s, Font f ) {
    return getTextDimension( s, f ).x;
  }

  /**
   * Returns a Color the same as the passed color in a lighter hue.
   * 
   * @param rgb the color
   * @return the lighter color
   * @since 2.0
   */
  public static Color lighter( Color rgb ) {
    int r = rgb.getRed(), g = rgb.getGreen(), b = rgb.getBlue();
    return new Color( null,
                      Math.max( 2, Math.min( ( int )( r / RGB_VALUE_MULTIPLIER ), 255 ) ),
                      Math.max( 2, Math.min( ( int )( g / RGB_VALUE_MULTIPLIER ), 255 ) ),
                      Math.max( 2, Math.min( ( int )( b / RGB_VALUE_MULTIPLIER ), 255 ) ) );
  }

  /**
   * Produces a ghosting effect on the shape <i>s</i>.
   * 
   * @param s the shape
   * @return the ghosted shape
   * @since 2.0
   */
  public static Shape makeGhostShape( Shape s ) {
    s.setBackgroundColor( ghostFillColor() );
    s.setFillXOR( true );
    s.setOutlineXOR( true );
    return s;
  }

  /**
   * Mixes the passed Colors and returns the resulting Color.
   * 
   * @param c1 the first color
   * @param c2 the second color
   * @param weight the first color's weight from 0-1
   * @return the new color
   * @since 2.0
   */
  public static Color mixColors( Color c1, Color c2, double weight ) {
    return new Color( null,
                      ( int )( c1.getRed() * weight + c2.getRed() * ( 1 - weight ) ),
                      ( int )( c1.getGreen() * weight + c2.getGreen() * ( 1 - weight ) ),
                      ( int )( c1.getBlue() * weight + c2.getBlue() * ( 1 - weight ) ) );
  }

  /**
   * Mixes the passed Colors and returns the resulting Color.
   * 
   * @param c1 the first color
   * @param c2 the second color
   * @return the new color
   * @since 2.0
   */
  public static Color mixColors( Color c1, Color c2 ) {
    return new Color( null,
                      ( c1.getRed() + c2.getRed() ) / 2,
                      ( c1.getGreen() + c2.getGreen() ) / 2,
                      ( c1.getBlue() + c2.getBlue() ) / 2 );
  }

  /**
   * Paints a border with an etching effect, having a shadow of Color <i>shadow</i> and highlight of
   * Color <i>highlight</i>.
   * 
   * @param g the graphics object
   * @param r the bounds of the border
   * @param shadow the shadow color
   * @param highlight the highlight color
   * @since 2.0
   */
  public static void paintEtchedBorder( Graphics g, Rectangle r, Color shadow, Color highlight ) {
    int x = r.x, y = r.y, w = r.width, h = r.height;
    g.setLineStyle( Graphics.LINE_SOLID );
    g.setLineWidth( 1 );
    g.setXORMode( false );
    w -= 2;
    h -= 2;
    g.setForegroundColor( shadow );
    g.drawRectangle( x, y, w, h );
    x++ ;
    y++ ;
    g.setForegroundColor( highlight );
    g.drawRectangle( x, y, w, h );
  }

  /**
   * Helper method to paint a grid. Painting is optimized as it is restricted to the Graphics' clip.
   * 
   * @param g The Graphics object to be used for painting
   * @param f The figure in which the grid is to be painted
   * @param origin Any point where the grid lines are expected to intersect
   * @param distanceX Distance between vertical grid lines; if 0 or less, vertical grid lines will
   *          not be drawn
   * @param distanceY Distance between horizontal grid lines; if 0 or less, horizontal grid lines
   *          will not be drawn
   * @since 3.0
   */
  public static void paintGrid( Graphics g,
                                IFigure f,
                                org.eclipse.draw2d.geometry.Point origin,
                                int distanceX,
                                int distanceY )
  {
    Rectangle clip = g.getClip( Rectangle.getSINGLETON() );
    if( distanceX > 0 ) {
      if( origin.x >= clip.x )
        while( origin.x - distanceX >= clip.x )
          origin.x -= distanceX;
      else
        while( origin.x < clip.x )
          origin.x += distanceX;
      for( int i = origin.x; i < clip.x + clip.width; i += distanceX )
        g.drawLine( i, clip.y, i, clip.y + clip.height );
    }
    if( distanceY > 0 ) {
      if( origin.y >= clip.y )
        while( origin.y - distanceY >= clip.y )
          origin.y -= distanceY;
      else
        while( origin.y < clip.y )
          origin.y += distanceY;
      for( int i = origin.y; i < clip.y + clip.height; i += distanceY )
        g.drawLine( clip.x, i, clip.x + clip.width, i );
    }
  }

  /**
   * Paints a border with an etching effect, having a shadow of a darker version of g's background
   * color, and a highlight a lighter version of g's background color.
   * 
   * @param g the graphics object
   * @param r the bounds of the border
   * @since 2.0
   */
  public static void paintEtchedBorder( Graphics g, Rectangle r ) {
    Color rgb = g.getBackgroundColor(), shadow = darker( rgb ), highlight = lighter( rgb );
    paintEtchedBorder( g, r, shadow, highlight );
  }

  /**
   * Sets Font to passed value.
   * 
   * @param f the new font
   * @since 2.0
   */
  protected static void setFont( Font f ) {
    FigureUtilities fu = instance();
    f = FontUtil.getFont( f.getFontData() );
    getGC().setFont( f );
    fu.appliedFont = f;
    fu.metrics = null;
  }

  /**
   * Returns the figure which is the nearest common ancestor of both figures, or <code>null</code>
   * if there is no common ancestor. A figure is an ancestor if it is the parent of another figure,
   * or if it is the ancestor of that figure's parent. If one figure is the ancestor of the other,
   * it is returned as the common ancestor.
   * 
   * @since 3.1
   * @param l left
   * @param r right
   * @return the common ancestor, if it exists, or <code>null</code>.
   */
  public static IFigure findCommonAncestor( IFigure l, IFigure r ) {
    if( l == r )
      return l;
    ArrayList left = new ArrayList();
    ArrayList right = new ArrayList();
    while( l != null ) {
      left.add( l );
      l = l.getParent();
    }
    while( r != null ) {
      right.add( r );
      r = r.getParent();
    }
    if( left.isEmpty() || right.isEmpty() )
      return null;
    for( int i = 0; i < left.size(); i++ ) {
      if( right.contains( left.get( i ) ) )
        return ( IFigure )left.get( i );
    }
    return null;
  }

  /**
   * Returns <code>true</code> if the ancestor contains the descendant, or is the ancestor of the
   * descendant's parent.
   * 
   * @param ancestor the ancestor
   * @param descendant the descendant
   * @return <code>true</code> if ancestor
   * @since 3.2
   */
  public static boolean isAncestor( final IFigure ancestor, IFigure descendant ) {
    while( descendant != null ) {
      descendant = descendant.getParent();
      if( descendant == ancestor )
        return true;
    }
    return false;
  }

  /**
   * Determines whether the given figure is showing and not (completely) clipped.
   * 
   * @param figure The figure to test
   * @return <code>true</code> if the given figure is showing and not completely clipped,
   *         <code>false</code> otherwise.
   * @since 3.7
   */
  public static boolean isNotFullyClipped( IFigure figure ) {
    if( figure == null || !figure.isShowing() ) {
      return false;
    }
    // check if figure is clipped
    // TODO: IClippingStrategy has to be taken into consideration as well.
    Rectangle figBounds = figure.getBounds().getCopy();
    IFigure walker = figure.getParent();
    while( !figBounds.isEmpty() && walker != null ) {
      walker.translateToParent( figBounds );
      figBounds.intersect( walker.getBounds() );
      walker = walker.getParent();
    }
    return !figBounds.isEmpty();
  }
}
